package org.yuyao.gateway.filter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 限流方案实践1
 * 基于最简单的限流实践操作,每个客户端，给定的时间窗口内限制请求次数
 * 不需要任何配置文件辅助
 */
//@Component
public class RateLimitingFilter implements GlobalFilter, Ordered {

    // 创建日志记录器
    private static final Logger logger = LoggerFactory.getLogger(RateLimitingFilter.class);


    //用户存储每个客户端的请求计数和时间戳
    private final ConcurrentMap<String,RequestCount> requestCounts=new ConcurrentHashMap<String, RequestCount>() ;

    //设定时间窗口大小
    private final static Duration TIME_WINDOW=Duration.ofSeconds(5);

    //设定限流阀值
    private final static int LIMIT=3;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1第一步，获取客户端唯一的标识，比如IP或者是用户ID
        String clientId=getClientId(exchange);
        logger.info("请求的客户端IP:{}",clientId);
        //2第二步，检查是否在限流范围内，如果限流，直接返回不在处理
        if(isRateLimited(clientId)){
            logger.info("命中限流IP:{}",clientId);
            return handleRateLimitedResponse(exchange);
        }
        //3第三步，更新请求计数好时间戳
        updateRequestCount(clientId);
        //4第四步，继续处理请求链路
        return chain.filter(exchange);
    }

    /**
     * 保存或更新客户端用户的访问次数
     * @param clientId
     */
    private void updateRequestCount(String clientId) {
        RequestCount existCount =requestCounts.get(clientId);
        if(existCount==null){
            //如果不存在，就给当前客户端IP初始化存在计数器当中
            requestCounts.put(clientId,new RequestCount(1,LocalDateTime.now()));
        }else{
            //如果存在则给当前客户端的访问次数加1
            existCount.increment();
        }
    }


    /**
     * 限流处理响应返回处理
     * @param exchange
     * @return
     */
    private Mono<Void> handleRateLimitedResponse(ServerWebExchange exchange) {
        ServerHttpResponse response=exchange.getResponse();
        response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
        return response.setComplete();

    }


    /**
     * 判断用户是否在限流范围内
     * @param clientId
     * @return
     */
    private boolean isRateLimited(String clientId) {
        RequestCount requestCount=requestCounts.get(clientId);
        if(requestCount==null){
            return false;
        }
        LocalDateTime now=LocalDateTime.now();
        if(now.isAfter(requestCount.getTimestamp().plus(TIME_WINDOW))){
            //时间窗口已过，重置计数
            requestCounts.remove(clientId);
            return false;
        }
        return requestCount.getCount().get()>=LIMIT;
    }


    /**
     * 获取用户请求客户端的IP地址
     * @param exchange
     * @return 返回客户端的IP地址
     */
    private String getClientId(ServerWebExchange exchange) {
        return exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
    }

    /**
     * 设置这个过滤器的执行顺序，保证他尽早执行，值越小，越先执行
     * @return
     */
    @Override
    public int getOrder() {
        return -1;
    }


    /**
     * 定义一个内部类来存储每个请求和时间戳
     */
    private static class RequestCount{
        private final AtomicInteger count;

        private final LocalDateTime timestamp;

        public RequestCount(Integer initialCount, LocalDateTime timestamp) {
            this.count = new AtomicInteger(initialCount);
            this.timestamp = timestamp;
        }

        public AtomicInteger getCount() {
            return count;
        }

        public LocalDateTime getTimestamp() {
            return timestamp;
        }

        public void increment() {
            //以原子的方式讲当前值加1，相当于相当于线程安全版本的++i的操作。
            count.incrementAndGet();
        }
    }
}
